/**
 * 
 */
package de.fsujena.chat_pi.server;

import java.io.*;
import java.util.Date;

import de.fsujena.chat_pi.message.SysMessage;
import de.fsujena.chat_pi.message.SysMessage.CATEGORY;
import de.fsujena.chat_pi.message.SysMessage.MESSAGE;
import de.fsujena.chat_pi.message.TextMessage;
import de.fsujena.chat_pi.user.User.ROLE;
import de.fsujena.chat_pi.user.UserManager;

/**
 * Server implementation. It holds user and room manager and starts a network listener on 
 * a specific port. It also offers methods to display messages for the server.
 * 
 * @author Erik Hebestreit
 */
public class Server {
	
	UserManager userManager;
	NetworkListener networkListener;
	boolean isStarted=false;
	BufferedReader input = new BufferedReader(new InputStreamReader(System.in));	
	
	private int port = 3141;

	/**
	 * @param args port can optionally be specified
	 * @throws IOException 
	 */
	public static void main(String[] args) {
		// create a new instance of the server object
		new Server().startServer(args);
	}
	
	/**
	 * Constructor. It launches the user manager and the network listener.
	 * 
	 * @param args arguments forwarded from main()
	 */
	public void startServer(String[] args) {
		// initialize user manager
		postInfo("Welcome");
		userManager = new UserManager();
		
		//startNetworkListener(args);
		
		if (args.length >= 1) {
			startNetworkListener(args);
			this.isStarted = true;
		}
		
		while (true) {
			String cmd = null;
			try {
				cmd = input.readLine();		//read from console

			} catch (Exception e) {
				e.printStackTrace();

			}

			if (cmd.startsWith("$")) {		//checks if input is a command
				// check the command
				commander(cmd);				//yes? call commander
			}else {
				sendTextMessage(cmd);
				}
			
		}
	}
	
	
	public void startNetworkListener(String[] args) {
		// if port is specified by input argument, it is set - else the default port stays set
		if (args.length > 0) {
			try {
				if (!this.setPort(Integer.parseInt(args[0]))) // if parsed integer is not a valid port number (checked in setPort)
					postWarning(String.format("Port %i is not valid. Default port %i used instead.", Integer.parseInt(args[0]), this.getPort()));
		    } catch (NumberFormatException e) {
		        postWarning("Argument must be an integer.");
		    }
		}
		
		try {
			// launch network listener on specific port
			networkListener = new NetworkListener(this.getPort(),this);
			
			/*
			 *  start network listener thread which handles login, registration and
			 *  listens for conventional messages
			 */
			networkListener.start();
			
			postInfo(String.format("server started on port %d",this.getPort()));
		} catch (IOException e) {e.printStackTrace();}
	}
	
	/**
	 * Handler to post standard info messages for the server.
	 * 
	 * @param message info message to be posted
	 */
	public void postInfo(String message) {
		System.out.println("INFO>> " + message);
	}
	
	/**
	 * Handler to post warnings for the server.
	 * 
	 * @param message warning to be posted
	 */
	public void postWarning(String message) {
		System.out.println("WARNING>> " + message);
	}
	
	/**
	 * Handler to post errors for the server.
	 * 
	 * @param message error to be posted
	 */
	public void postError(String message) {
		System.err.println("ERROR>> " + message);
	}
	
	/**
	 * Handler to post text messages for the server.
	 * 
	 * @param message text to be posted
	 */
	public void postTextMessage(String user, String message) {
		System.out.println(user.toUpperCase() + ">> " + message);
	}
	
	/**
	 * Set the port. Note that this setting only takes effect for a newly created NetworkListener.
	 * 
	 * @param port port number where the server should listen
	 * @return <b>true</b> if port was set successfully,
	 * <b>false</b> if port number exceeded possible port numbers
	 */
	public boolean setPort(int port) {
		if (port > 1024 && port <= 65535) {
			this.port = port;
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Get the currently set port.
	 * 
	 * @return port number where the server is listening
	 */
	public int getPort() {
		return this.port;
	}
	
	
	/**
	 * process the user input
	 * 
	 * @param command contains the user input
	 */
	public void commander(String command) {
		
		String[] arg = command.split(" "); 					//splits input on every " " (arg[0] should contain"$")

		if (arg[1].equals("start") && arg.length >= 3 && arg[2].equals("server")) {				//starts the server
			if(arg.length==4){
			start(arg[3]);
			}else{
				postError("invalid number of arguments");
			}
		} else if (arg[1].equals("make") && arg.length >= 4 && arg[3].equals("admin")) {			//set userrights to admin
			userManager.setUserRole(arg[2], ROLE.ADMIN);
		} else if (arg[1].equals("make") && arg.length >= 4 && arg[3].equals("moderator")) {		//set userrights to moderator
			userManager.setUserRole(arg[2], ROLE.MODERATOR);
		} else if (arg[1].equals("make") && arg.length >= 4 && arg[3].equals("normal")) {			//set userrights to normal
			userManager.setUserRole(arg[2], ROLE.STANDARD);
		} else if (arg[1].equals("kick")&&arg[2].equals("user")) {									//kicks an user
			if(arg.length==4){
				kickUser(arg[3]);
			} else {
				postError("invalid number of arguments");
			}
		} else if (arg[1].equals("bann")&&arg[2].equals("user")) {									//banns an user from the server
			if (arg.length==4) {
				bannUser(arg[3]);
			} else {
				postError("invalid number of arguments");
			}
		} else if (arg[1].equals("unbann")&&arg[2].equals("user")) {								//unbann an user
			if (arg.length==4) {
				unbannUser(arg[3]);
			} else {
				postError("invalid number of arguments");
			}
		} else if (arg[1].equals("remove")&&arg[2].equals("user")) {									//removes an user permanatly
			if (arg.length==4) {
				removeUser(arg[3]);
			} else {
				postError("invalid number of arguments");
			}
		} else if (arg[1].equals("stop") && arg.length >= 3 && arg[2].equals("server")) {				//stops the server
			close();
		} else if (arg[1].equals("exit")) {										//exit the programm
			exit();
		} else {
			System.out.println("command not known");
		}
	}

	/**
	 * removes a user from the user register
	 * 
	 * @param name username of the user to be removed
	 */
	private void removeUser(String name) {
		int userNum = networkListener.userExists(name);
		if (userNum >= 0) {
			networkListener.messageListener.get(userNum).sendSysMessage(CATEGORY.STATUS, MESSAGE.YOU_ARE_REMOVED);
			networkListener.removeClient(name);
		}
		userManager.removeUser(name);
		return;
	}

	/**
	 * closes the server and cancels the connection
	 */
	public void close(){
		SysMessage tempMsg=new SysMessage(CATEGORY.CONNECTION,MESSAGE.SERVER_CLOSED);
		tempMsg.setSenderUsername("SERVER");
		tempMsg.setSent(new Date());
		networkListener.distributeMsg(tempMsg);											//tell the clients that the server shuts down
		networkListener.close();														//removes connections
		networkListener=null;															//free resources
		postInfo("server is closed");													//post info
		isStarted=false;
		return;
	}

	/**
	 * starts the server
	 * 
	 * @param portstr String containing the portnumber
	 */
	public void start(String portstr){
		String[] temp=new String[1];			//needed because startNetworkListener requires an array
		temp[0]=portstr;
		startNetworkListener(temp);
		isStarted=true;
		return;
	}

	/**
	 * closes the programm
	 */
	public void exit(){
		if(isStarted)close();				//at first cancel connection
		System.exit(0);						//stop the main thread	
		return;
	}

	/**
	 * sends a textmessage to all clients
	 * 
	 * @param msg string containing the message
	 */
	public void sendTextMessage(String msg){
		if(isStarted){
			TextMessage tempMsg=new TextMessage(msg);			//creates the message
			tempMsg.setSenderUsername("SERVER");
			tempMsg.setSent(new Date());
			networkListener.distributeMsg(tempMsg);				//distributes it
		}
		return;
	}
	
	/**
	 * kicks user from server
	 * 
	 * @param name username to be kicked
	 */
	public void kickUser(String name){
		SysMessage tempMsg= new SysMessage(CATEGORY.MODERATE,MESSAGE.KICK_USER,name);
		tempMsg.setSent(new Date());
		tempMsg.setSenderUsername(name);
		networkListener.distributeMsg(tempMsg);
		networkListener.removeClient(name);
		return;
	}
	
	/**
	 * banns an user from the server
	 * 
	 * @param name username to be banned
	 */
	public void bannUser(String name){
		int userNum = networkListener.userExists(name);
		if (userNum >= 0) {
			SysMessage tempMsg= new SysMessage(CATEGORY.MODERATE,MESSAGE.BANN_USER,name);
			tempMsg.setSent(new Date());
			tempMsg.setSenderUsername(name);
			networkListener.distributeMsg(tempMsg);
			networkListener.removeClient(name);
		}
		
		userManager.bannUser(name);
		return;
	}
	
	/**
	 * unbann an user
	 * 
	 * @param name username to be unbanned
	 */
	public void unbannUser(String name){
		userManager.unbannUser(name);
		return;
	}
}
